package com.fuluwa.domain.bank.service;

import java.math.BigDecimal;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.fuluwa.domain.bank.dto.AccountDTO;
import com.fuluwa.domain.bank.model.Account;
import com.fuluwa.domain.bank.model.Account.AccountType;
import com.fuluwa.domain.bank.model.GLAccount;
import com.fuluwa.domain.bank.model.GeneralLedger;
import com.fuluwa.domain.bank.model.Nobook;
import com.fuluwa.domain.bank.model.Nobook.NobookType;
import com.fuluwa.domain.bank.model.TransactionLog;
import com.fuluwa.domain.bank.model.TransactionLog.TransactionType;
import com.fuluwa.domain.bank.repository.AccountRepository;
import com.fuluwa.domain.bank.repository.NobookRepository;
import com.fuluwa.domain.security.model.User;
import com.fuluwa.infra.consts.AccountConsts;
import com.fuluwa.infra.exception.PersistenceException;
import com.fuluwa.infra.utils.MoneyFormat;

@Component
public class AccountServiceImpl implements AccountService {
	@Autowired
	AccountRepository accountRepository;
	@Autowired
	NobookRepository nobookRepository;

	@Override
	public Account openingAccount(User user, AccountType accountType) throws PersistenceException {
		Account account = new Account(user, accountType);
		account.save();
		return account;
	}

	@Override
	public void deposit(AccountDTO accountDTO) throws PersistenceException {
		doDepsit(accountDTO, true,TransactionType.DEPOSIT);
	}

	/**
	 * @param accountDTO
	 * @param needRecordLog for funds transfer,just record one log,but need do credit and debit .
	 * @param type
	 * @throws PersistenceException
	 */
	private void doDepsit(AccountDTO accountDTO, boolean needRecordLog,TransactionType type) throws PersistenceException {
		BigDecimal amount=accountDTO.getAmount();
		String accountNo = accountDTO.getAccountNo();
		String txtTxnDesc = accountDTO.getTxtTxnDesc();
		String referenceNo = accountDTO.getReferenceNo();
		String referenceUserNo = accountDTO.getReferenceUserNo();	
	
		Account account = accountRepository.findByAccountNo(accountNo);
		account.doCredit(amount);
		
		Nobook nobook=new Nobook(account,txtTxnDesc,amount, NobookType.CREDIT,type);
		nobook.save();
		
		if(needRecordLog) {
			TransactionLog log=new TransactionLog(account, referenceNo, referenceUserNo, txtTxnDesc, type, amount);
			log.save();
		}
		
		if(account.isCashAccount()) {
			new GLAccount(GeneralLedger.CashLibility.getGlCode(),amount, false).save();
			new GLAccount(GeneralLedger.CashAsset.getGlCode(),amount, true).save();
			
		}else {
			new GLAccount(GeneralLedger.GoldLibility.getGlCode(),amount, false).save();
			new GLAccount(GeneralLedger.GoldAsset.getGlCode(),amount, true).save();
		}
//		account.detach();
	}

	@Override
	public void withdrawl(AccountDTO accountDTO) throws PersistenceException {
		TransactionType type=TransactionType.WITHDRAWL;
		doWithdrawl(accountDTO, type);
	}

	private void doWithdrawl(AccountDTO accountDTO, TransactionType type) throws PersistenceException {
		BigDecimal amount=accountDTO.getAmount();
		Account account = accountRepository.findByAccountNo(accountDTO.getAccountNo());
		
		account.doDebit(amount);
		
		Nobook nobook=new Nobook(account,accountDTO.getTxtTxnDesc(),amount, NobookType.DEBIT,type);
		nobook.save();
		
		TransactionLog log=new TransactionLog(account, accountDTO.getReferenceNo(), accountDTO.getReferenceUserNo(), accountDTO.getTxtTxnDesc(), type, amount);
		log.save();
		
		if(account.isCashAccount()) {
			new GLAccount(GeneralLedger.CashLibility.getGlCode(),amount, true).save();
			new GLAccount(GeneralLedger.CashAsset.getGlCode(),amount, false).save();
			
//			new GLAccount(GeneralLedger.Income.getGlCode(),amount, false).save();	
		}else {
			new GLAccount(GeneralLedger.GoldLibility.getGlCode(),amount, true).save();
			new GLAccount(GeneralLedger.GoldAsset.getGlCode(),amount, false).save();
		}
//		account.detach();
	}

	@Override
	public void fundsTransfer(AccountDTO account) throws PersistenceException {
		if(!StringUtils.substring(account.getAccountNo(), 0, 3).equals(StringUtils.substring(account.getAccountNoTo(), 0, 3))) {
			throw new AccountException("Account type should be same for funds transfer");
		}
		doWithdrawl(account,TransactionType.FUNDXFER);
		account.setAccountNo(account.getAccountNoTo());
		doDepsit(account, false,TransactionType.FUNDXFER);
	}

	@Override
	public String queryBalance(String accountNo) throws PersistenceException {
		Account account = accountRepository.findByAccountNo(accountNo);
		return MoneyFormat.format(account.getAvailableBalance());
	}

	@Override
	public List<Nobook> queryFundsDetails(String accountNo) throws PersistenceException {
		Account account = accountRepository.findByAccountNo(accountNo);
		return nobookRepository.findByAccount(account);
	}

	@Override
	public void closureAccount(String accountNo) throws PersistenceException {
		Account account = accountRepository.findByAccountNo(accountNo);
		account.close();
		account.detach();
	}

	@Override
	public void reversal(String referenceNo) throws PersistenceException {
		// TODO Auto-generated method stub

	}

	@Override
	public void hold(AccountDTO accountDTO) throws PersistenceException {
		BigDecimal amount=accountDTO.getAmount();
		Account account = accountRepository.findByAccountNo(accountDTO.getAccountNo());
		account.hold(amount);
//		account.detach();
		new GLAccount(GeneralLedger.CashAsset.getGlCode(),amount, true).save();
		new GLAccount(GeneralLedger.ContigentLibility.getGlCode(),amount, false).save();
	}

	@Override
	public void unhold(AccountDTO accountDTO) throws PersistenceException {
		BigDecimal amount=accountDTO.getAmount();
		Account account = accountRepository.findByAccountNo(accountDTO.getAccountNo());
		account.unhold(amount);
//		account.detach();
		new GLAccount(GeneralLedger.CashAsset.getGlCode(),amount, false).save();
		new GLAccount(GeneralLedger.ContigentLibility.getGlCode(),amount, true).save();
	}

	@Override
	public void serviceCharge(AccountDTO accountDTO) throws PersistenceException {
		if(!AccountConsts.needServiceCharge) {
			return;
		}
		
		TransactionType type=TransactionType.WITHDRAWL;
		BigDecimal amount=accountDTO.getAmount();
		Account account = accountRepository.findByAccountNo(accountDTO.getAccountNo());

		//  I think we can move this function to another module.or we can supply api to take servicecharge , business to decide if need service charge or not.
		amount=amount.multiply(AccountConsts.SERVICE_CHARGE_RATE);
		account.doDebit(amount);

		Nobook nobook=new Nobook(account,"Service charge:"+accountDTO.getTxtTxnDesc(),amount, NobookType.DEBIT,type);
		nobook.save();

		if(account.isCashAccount()) {
			new GLAccount(GeneralLedger.ServiceCharge.getGlCode(),amount, false).save();
		}else {
			//TODO gold account need service charge also?
		}		
	}

}
